/*
 * Created on 08-Oct-2004
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package org.gudy.azureus2.core3.disk.impl;

/**
 * @author parg
 * @author MjrTom
 *			2005/Oct/08: startPriority/resumePriority handling and minor clock fixes
 *			2006/Jan/02: refactoring, change booleans to statusFlags
 */

import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList;

public class DiskManagerPieceImpl implements DiskManagerPiece {
    // private static final LogIDs LOGID = LogIDs.PIECES;

    private static final byte PIECE_STATUS_NEEDED = 0x01; // want to have the piece
    private static final byte PIECE_STATUS_WRITTEN = 0x20; // piece fully written to storage
    private static final byte PIECE_STATUS_CHECKING = 0x40; // piece is being hash checked

    private static final byte PIECE_STATUS_MASK_DOWNLOADABLE = PIECE_STATUS_CHECKING | PIECE_STATUS_WRITTEN | PIECE_STATUS_NEEDED;

    // 0x65; // Needed IS once again included in this

    private static final byte PIECE_STATUS_MASK_NEEDS_CHECK = PIECE_STATUS_CHECKING | PIECE_STATUS_WRITTEN;

    // private static boolean statusTested =false;

    private final DiskManagerHelper diskManager;
    private final int pieceNumber;

    /** the number of blocks in this piece: can be short as this gives up to .5GB piece sizes with 16K blocks */

    private final short nbBlocks;

    // to save memory the "written" field is only maintained for pieces that are
    // downloading. A value of "null" means that either the piece hasn't started
    // download or that it is complete.
    // access to "written" is single-threaded (by the peer manager) apart from when
    // the disk manager is saving resume data.
    // actually this is not longer strictly true, as setDone is called asynchronously
    // however, this issue can be worked around by working on a reference to the written data
    // as problems only occur when switching from all-written to done=true, both of which signify
    // the same state of affairs.

    protected volatile boolean[] written;

    private byte statusFlags;

    /**
     * it's *very* important to accurately maintain the "done" state of a piece. Currently the statusFlags are updated in a non-thread-safe manner so
     * a 'done' field is maintained seperatly. Synchronizing access to statusFlags or done would cause a tremendous performance hit.
     */
    private short read_count;

    private boolean done;

    public DiskManagerPieceImpl(final DiskManagerHelper _disk_manager, final int pieceIndex, int length) {
        diskManager = _disk_manager;
        pieceNumber = pieceIndex;

        nbBlocks = (short) ((length + DiskManager.BLOCK_SIZE - 1) / DiskManager.BLOCK_SIZE);

        statusFlags = PIECE_STATUS_NEEDED;
    }

    public DiskManager getManager() {
        return diskManager;
    }

    public int getPieceNumber() {
        return pieceNumber;
    }

    /**
     * @return int number of bytes in the piece
     */
    public int getLength() {
        return (diskManager.getPieceLength(pieceNumber));
    }

    public int getNbBlocks() {
        return nbBlocks;
    }

    public short getReadCount() {
        return (read_count);
    }

    public void setReadCount(short c) {
        read_count = c;
    }

    public int getBlockSize(final int blockNumber) {
        if (blockNumber == nbBlocks - 1) {

            int len = getLength() % DiskManager.BLOCK_SIZE;

            if (len != 0) {

                return (len);
            }
        }

        return DiskManager.BLOCK_SIZE;
    }

    public boolean isSkipped() {
        final DMPieceList pieceList = diskManager.getPieceList(pieceNumber);
        for (int i = 0; i < pieceList.size(); i++) {
            if (!pieceList.get(i).getFile().isSkipped()) {
                return (false);
            }
        }
        return (true);
    }

    public boolean isNeeded() {
        return (statusFlags & PIECE_STATUS_NEEDED) != 0;
    }

    public boolean calcNeeded() {
        boolean filesNeeded = false;
        final DMPieceList pieceList = diskManager.getPieceList(pieceNumber);
        for (int i = 0; i < pieceList.size(); i++) {
            final DiskManagerFileInfoImpl file = pieceList.get(i).getFile();
            final long fileLength = file.getLength();
            filesNeeded |= fileLength > 0 && file.getDownloaded() < fileLength && !file.isSkipped();
        }
        if (filesNeeded) {
            statusFlags |= PIECE_STATUS_NEEDED;
            return true;
        }
        statusFlags &= ~PIECE_STATUS_NEEDED;
        return false;
    }

    public void clearNeeded() {
        statusFlags &= ~PIECE_STATUS_NEEDED;
    }

    public void setNeeded() {
        statusFlags |= PIECE_STATUS_NEEDED;
    }

    public void setNeeded(boolean b) {
        if (b)
            statusFlags |= PIECE_STATUS_NEEDED;
        else
            statusFlags &= ~PIECE_STATUS_NEEDED;
    }

    public boolean isWritten() {
        return (statusFlags & PIECE_STATUS_WRITTEN) != 0;
    }

    /**
     * written[] can be null, in which case if the piece is Done, all blocks are complete otherwise no blocks are complete
     */
    public boolean[] getWritten() {
        return written;
    }

    public boolean isWritten(final int blockNumber) {
        if (done)
            return true;
        final boolean[] writtenRef = written;
        if (writtenRef == null)
            return false;
        return writtenRef[blockNumber];
    }

    public int getNbWritten() {
        if (done)
            return nbBlocks;
        final boolean[] writtenRef = written;
        if (writtenRef == null)
            return 0;
        int res = 0;
        for (int i = 0; i < nbBlocks; i++) {
            if (writtenRef[i])
                res++;
        }
        return res;
    }

    public void setWritten(final int blockNumber) {
        if (written == null)
            written = new boolean[nbBlocks];
        final boolean[] written_ref = written;

        written_ref[blockNumber] = true;
        for (int i = 0; i < nbBlocks; i++) {
            if (!written_ref[i])
                return;
        }
        statusFlags |= PIECE_STATUS_WRITTEN;
    }

    public boolean isChecking() {
        return (statusFlags & PIECE_STATUS_CHECKING) != 0;
    }

    public void setChecking() {
        statusFlags |= PIECE_STATUS_CHECKING;
    }

    public boolean isNeedsCheck() {
        return !done && (statusFlags & PIECE_STATUS_MASK_NEEDS_CHECK) == PIECE_STATUS_WRITTEN;
    }

    // this cannot be implemented the same as others could be
    // because the true state of Done is only determined by
    // having gone through setDoneSupport()
    public boolean calcDone() {
        return done;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean b) {
        // we delegate this operation to the disk manager so it can synchronise the activity
        if (b != done) {
            diskManager.setPieceDone(this, b);
        }
    }

    /**
     * this is ONLY used by the disk manager to update the done state while synchronized i.e. don't use it else where!
     * 
     * @param b
     */

    public void setDoneSupport(final boolean b) {
        done = b;
        if (done)
            written = null;
    }

    public void setDownloadable() {
        setDone(false);
        statusFlags &= ~(PIECE_STATUS_MASK_DOWNLOADABLE);
        calcNeeded(); // Needed wouldn't have been calced before if couldn't download more
    }

    public boolean isDownloadable() {
        return !done && (statusFlags & PIECE_STATUS_MASK_DOWNLOADABLE) == PIECE_STATUS_NEEDED;
    }

    /**
     * @return true if the piece is Needed and not Done
     */
    public boolean isInteresting() {
        return !done && (statusFlags & PIECE_STATUS_NEEDED) != 0;
    }

    public void reset() {
        setDownloadable();
        written = null;
    }

    public void reDownloadBlock(int blockNumber) {
        final boolean[] written_ref = written;
        if (written_ref != null) {
            written_ref[blockNumber] = false;
            setDownloadable();
        }
    }

    /*
     * public static final void testStatus() { if (statusTested) return; statusTested =true; int originalStatus =statusFlags; for (int i =0; i <0x100;
     * i++) { statusFlags =i; Logger.log(new LogEvent(this, LOGID, LogEvent.LT_INFORMATION, "Done:" +isDone() +"  Checking:" +isChecking()
     * +"  Written:" +isWritten() +"  Downloaded:" +isDownloaded() +"  Requested:" +isRequested() +"  Needed:" +isNeeded() +"  Interesting:"
     * +isInteresting() +"  Requestable:" +isRequestable() +"  EGMActive:" +isEGMActive() +"  EGMIgnored:" +isEGMIgnored() )); } statusFlags
     * =originalStatus; }
     */

    public String getString() {
        String text = "";

        text += (isNeeded() ? "needed," : "");
        text += (isDone() ? "done," : "");

        if (!isDone()) {
            text += (isDownloadable() ? "downable," : "");
            text += (isWritten() ? "written" : ("written " + getNbWritten())) + ",";
            text += (isChecking() ? "checking" : "");
        }

        if (text.endsWith(",")) {
            text = text.substring(0, text.length() - 1);
        }
        return (text);
    }
}
